
using Boilen.Guards;
using Boilen.Primitives.CodeGeneration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit;
using Xunit.Extensions;


namespace Boilen.Primitives.Implementers {

    public class TestMutableProperty : TestImplementers {

        [Theory]
        [PropertyData( "SourceTypes" )]
        public void code_compiles_for_1_MutableProperty( Type sourceType ) {
            var pci = MemberTestInfo.Create<int>( "intProperty" );

            var pt = Partial.Type( sourceType )
                .AddMutableProperty<int>( pci.ParameterName, pci.Description )
                ;
            var members = Compile.PartialType( pt );

            CheckMutableProperties( members, pci );
            CheckMutableConstructors( members, pci );
        }

        [Theory]
        [PropertyData( "SourceTypes" )]
        public void code_compiles_for_1_MutableProperty_with_default_value( Type sourceType ) {
            var pci = MemberTestInfo.Create<int>( "intProperty", 1 );

            var pt = Partial.Type( sourceType )
                .AddMutableProperty<int>( pci.ParameterName, pci.Description, p => p
                    .SetDefaultValue( pci.DefaultValue )
                )
                ;
            var members = Compile.PartialType( pt );

            CheckMutableProperties( members, pci );
            CheckMutableConstructors( members, pci );
        }

        [Theory]
        [PropertyData( "SourceTypes" )]
        public void code_compiles_for_1_MutableProperty_with_default_value_with_external_documentation( Type sourceType ) {
            var pci = MemberTestInfo.Create<int>( "intProperty", 1 );

            using( ChangeExternalDocumentationPrefix( ) ) {
                var pt = Partial.Type( sourceType )
                    .AddMutableProperty<int>( pci.ParameterName, p => p
                        .SetDefaultValue( pci.DefaultValue )
                    )
                    ;
                var members = Compile.PartialType( pt );

                CheckMutableProperties( members, pci );
                CheckMutableConstructors( members, pci );
            }
        }

        [Theory]
        [PropertyData( "SourceTypes" )]
        public void code_compiles_for_1_MutableProperty_without_equality_test( Type sourceType ) {
            var pci = MemberTestInfo.Create<int>( "intProperty" );

            var pt = Partial.Type( sourceType )
                .AddMutableProperty<int>( pci.ParameterName, pci.Description, p => p
                    .SetEqualityFormat( null )
                )
                ;
            var members = Compile.PartialType( pt );

            CheckMutableProperties( members, pci );
            CheckMutableConstructors( members, pci );
        }

        [Theory]
        [PropertyData( "SourceTypes" )]
        public void code_compiles_for_1_MutableProperty_with_guard( Type sourceType ) {
            var pci = MemberTestInfo.Create<int>( "intProperty" );

            var pt = Partial.Type( sourceType )
                .AddMutableProperty<int>( pci.ParameterName, pci.Description, p => p
                    .SetSetterAccessibility( Accessibility.Private )
                    .NotNull( null )
                )
                ;
            var members = Compile.PartialType( pt );

            var properties = CheckMutableProperties( members, pci );
            CheckMutableConstructors( members, pci );

            int intPropertyValue = 0;
            var property = properties.Single( );
            object instance = Activator.CreateInstance( property.DeclaringType );
            TestGuards.UseContext(
                ( ) => property.SetValue( instance, intPropertyValue, null ),
                context => Assert.True( context.NotNullCalled )
            );
        }

        [Theory]
        [PropertyData( "SourceTypes" )]
        public void code_compiles_for_1_MutableProperty_with_alias_type( Type sourceType ) {
            var pci = MemberTestInfo.Create<TypeAlias>( "property" );

            var pt = Partial.Type( sourceType )
                .AddMutableProperty<TypeAlias>( pci.ParameterName, pci.Description )
                ;
            var members = Compile.PartialType( pt );
        }


        #region Utility

        public static IEnumerable<object[]> SourceTypes { get { return SourceTypesCore; } }

        private static PropertyInfo[] CheckMutableProperties( IEnumerable<MemberInfo> members, params MemberTestInfo[] propInfos ) {
            return CheckProperties( members, propInfos, property => {
                Assert.True( property.CanRead );
                Assert.True( property.CanWrite );
            } );
        }

        private static ConstructorInfo[] CheckMutableConstructors( IEnumerable<MemberInfo> members, params MemberTestInfo[] propInfos ) {
            Type declaringType = members.First( ).DeclaringType;
            bool isStruct = declaringType.IsValueType;

            bool hasDefaultConstructor = !isStruct;
            int expectedConstructorCount = hasDefaultConstructor ? 1 : 0;


            var constructors = CheckConstructors( members, expectedConstructorCount, propInfos );

            if( hasDefaultConstructor ) {
                var partialConstructor = constructors[0];
                var partialParameters = partialConstructor.GetParameters( );
                CheckParameters( partialParameters, new MemberTestInfo[0] );
            }


            return constructors;
        }


        [AliasType( typeof( string ) )]
        private class TypeAlias { }

        #endregion

    }

}
